﻿#include "opencv2/opencv.hpp"
//#include <opencv2/core/core.hpp>
//#include <opencv2/highgui/highgui.hpp>
//#include <opencv2/imgproc/imgproc.hpp>

using namespace cv;
using namespace std;

int labelTargets(Mat &src, Mat &mask, int thresh = 100);

//1. 在测试视频(OpenCV安装目录\sources\samples\data)上，使用基于混合高斯模型的背景提取算法，提取前景并显示(显示二值化图像，前景为白色)。 
//2. 在1基础上，将前景目标进行分割，进一步使用不同颜色矩形框标记，并在命令行窗口中输出每个矩形框的位置和大小。 

/*
1. 视频采集(取到视频中当前帧图像) :VideoCapture
2. 图像预处理 :threshold 取二进制阈值化                     
3. 背景提取 ：getBackgroundImage
4. 前景分割 ：findContours
5. 图像特征描述及目标分析
6. 得到最终结果
*/

int main()
{
	//char *fn = (char *)"E:\\AI\\AIsoftware\\opencv\\sources\\samples\\data\\vtest.avi";
	string path = "E:\\AI\\AIsoftware\\opencv\\sources\\samples\\data\\vtest.avi";
	VideoCapture cap;   //读取本地视频和打开摄像头
	Mat source, image, foreGround, backGround, fgMask;	

	//定义一个类用于背景提取
	//高斯混合模型背景建模BackgroundSubtractorMOG2用于动态目标检测
    Ptr<BackgroundSubtractor> pBgModel = createBackgroundSubtractorMOG2().dynamicCast<BackgroundSubtractor>(); 

	double scale = 0.5;  //放缩倍数

	cap.open(path); 
	if (!cap.isOpened())
	{
		cout << "无法打开视频文件：" << path << endl;
		//return;
	}

	for (;;)
	{
		cap >> source;       //获取当前帧
		if (source.empty())
		{
			break;
		}

		Size ResImgSiz = Size(round(source.cols*scale), round(source.rows*scale));   //Size()函数：先列后行
		resize(source, image, ResImgSiz, INTER_LINEAR);  //缩小图像的大小为原来的1/2，ResImgSiz(width，height)指定输出图像的大小，INTER_LINEAR双线性插值

		if (foreGround.empty())   //创建空白背景
		{
			foreGround.create(image.size(), image.type());   //创建新的矩阵
		}

		pBgModel->apply(image, fgMask);  //指定一个指针更新背景，fgMask前景掩膜，二值化图像

		//GaussianBlur(fgMask,fgMask,Size(5,5),0);
		threshold(fgMask, fgMask, 30, 255, THRESH_BINARY);  //去噪，THRESH_BINARY二进制阈值化：当前阈值30，最大阈值255，即(value>30?255:0)。

		foreGround = Scalar::all(0);
		image.copyTo(foreGround, fgMask);  //可以得到一个有掩膜fgMask的矩阵foreGround
		//标记找到的运动目标
		int nTargets = labelTargets(image, fgMask);
		cout << "共检测到 " << nTargets << " 个目标" << endl;

		pBgModel->getBackgroundImage(backGround);

		//显示原始图像及背景，前景
		imshow("Source", image);
		imshow("Background", backGround);
		imshow("Foreground", foreGround);
		imshow("Foreground Mask", fgMask);

		//以下检查是否终止（按下ESC终止，对应ASCII 27
		char key = waitKey(100); //每一帧等待100ms
		if (key==27)
		{
			break;
		}
	}
	waitKey(0);
}

//运动目标检测，标记
int labelTargets(Mat &src, Mat &mask, int thresh)
{
	//以下是图像分割
	Mat seg = mask.clone();
	vector<vector<Point>> cnts;
	findContours(seg, cnts, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

	//以下进行筛选
	float area;
	Rect rect;
	int count = 0;
	string strCount;
	for (int i = cnts.size()-1; i >=0; i--)
	{
		vector<Point> c = cnts[i]; //每个目标对应的轨迹
		area = contourArea(c);  //计算整个或部分轮廓的面积
		if (area<thresh) //滤除面积小于thresh的分割结果：可能是噪声
		{
			continue;
		}

		count++; //统计运动目标数量
		//cout<<"blob"<<i<<":"<<area<<endl;
		
		rect = boundingRect(c);   //提取矩形坐标，boundingRect()计算轮廓的垂直边界最小矩形，并给出位置
		
		cout << "面积："<< area <<" 位置： x:" << rect.x << " y:" << rect.y << endl;
		
		rectangle(src, rect, Scalar(0, 0, 0xff), 1);  //在原始图像上画出包围矩形，并给每个矩形标号

		stringstream ss;
		ss << count;
		ss >> strCount;
		putText(src, strCount, Point(rect.x, rect.y), CV_FONT_HERSHEY_PLAIN, 0.5, Scalar(0, 0xff, 0));
	}
	return count;
}